﻿using DotNetty.Transport.Channels;
using FileCommon.Models;
using FileEfCore.DbContexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace FileClient.Core.Client.Processor;

public class ClientFileProcessor : IClientFileProcessor
{
    private readonly int _filePieceSize;
    private readonly string _serverFileName;
    private readonly string _localFilePath;

    public ClientFileProcessor(int filePieceSize, string localFilePath, string serverFileName)
    {
        _filePieceSize = filePieceSize;
        _serverFileName = serverFileName;
        _localFilePath = localFilePath;
    }

    public async Task ClientReadFile(IChannelHandlerContext context, FileStream fileStream, string hashMd5)
    {
        var lastLength = fileStream.Length <= _filePieceSize ? (int)fileStream.Length : _filePieceSize;
        var buffer = new byte[lastLength];
        fileStream.Seek(0, SeekOrigin.Begin);
        var byteRead = fileStream.Read(buffer, 0, lastLength);
        if (byteRead != -1)
        {
            var uploadFileInfo = new UploadFileInfo()
            {
                FileName = _serverFileName,
                StartPos = 0,
                FileLength = fileStream.Length,
                Bytes = buffer,
                Count = lastLength,
                Complete = byteRead == fileStream.Length,
                OperationType = OperationType.Upload,
                MD5Hash = hashMd5
            };
            await context.WriteAndFlushAsync(uploadFileInfo);
        }
    }

    public async Task ClientReadFile(IChannelHandlerContext context, UploadFileInfo uploadFileInfo,
        FileStream fileStream)
    {
        var start = uploadFileInfo.StartPos;
        if (start == -1)
        {
            await context.CloseAsync();
            return;
        }

        if (uploadFileInfo.Complete)
        {
            await context.CloseAsync();
            return;
        }

        var remainingLength = fileStream.Length - start;
        var complete = false;
        var lastLength = _filePieceSize;
        if (remainingLength < _filePieceSize)
        {
            lastLength = (int)remainingLength;
            complete = true;
        }

        var buffer = new byte[lastLength];
        fileStream.Seek(start, SeekOrigin.Begin);
        var byteRead = fileStream.Read(buffer, 0, lastLength);
        if (byteRead != -1 && fileStream.Length - start > 0)
        {
            uploadFileInfo.Count = lastLength;
            uploadFileInfo.Bytes = buffer;
            uploadFileInfo.Complete = complete;
            await context.WriteAndFlushAsync(uploadFileInfo);
        }
        else
        {
            await context.CloseAsync();
        }
    }

    public async Task ClientWriteFile(IChannelHandlerContext context, UploadFileInfo uploadFileInfo)
    {
        if (File.Exists(_localFilePath))
        {
            throw new Exception($"本地文件已经存在文件{Path.GetFileName(_localFilePath)}");
        }

        await context.WriteAndFlushAsync(uploadFileInfo);
    }

    public async Task ClientWriteFile(IChannelHandlerContext context, UploadFileInfo uploadFileInfo,
        FileStream fileStream)
    {
        if (!uploadFileInfo.Success)
        {
            throw new Exception(uploadFileInfo.ErrorMessage);
        }

        if (uploadFileInfo.FileLength == 0)
        {
            await context.WriteAndFlushAsync(uploadFileInfo);
            return;
        }

        if (uploadFileInfo.StartPos != 0)
        {
            await using var dbContext = App.ServiceProvider.GetRequiredService<FileDbContext>();
            var fileInfo = await dbContext.UploadFiles.FirstOrDefaultAsync(p =>
                p.LocalFileName == _localFilePath && p.ServerFileName == _serverFileName);
            if (fileInfo != null && fileInfo.MD5Hash != uploadFileInfo.MD5Hash)
            {
                throw new Exception($"已经存在文件{_localFilePath}");
            }
        }

        fileStream.Seek(uploadFileInfo.StartPos, SeekOrigin.Begin);
        if (uploadFileInfo.Count > 0)
        {
            await fileStream.WriteAsync(uploadFileInfo.Bytes, 0, uploadFileInfo.Count);
        }

        if (uploadFileInfo.Complete)
        {
            await context.CloseAsync();
            return;
        }

        uploadFileInfo.Bytes = Array.Empty<byte>();
        uploadFileInfo.StartPos += uploadFileInfo.Count;
        await context.WriteAndFlushAsync(uploadFileInfo);
    }
}